package com.stars.easyms.rest.handler;

import com.stars.easyms.base.annotation.CustomToString4Log;
import com.stars.easyms.base.interceptor.AbstractEasyMsMethodInterceptor;
import com.stars.easyms.base.util.JsonUtil;
import com.stars.easyms.base.util.ReflectUtil;
import com.stars.easyms.rest.bean.EasyMsRestContext;
import com.stars.easyms.rest.bean.RestInfo;
import com.stars.easyms.rest.properties.EasyMsRestProperties;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.io.InputStreamSource;
import org.springframework.http.HttpEntity;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.HashMap;
import java.util.Map;

/**
 * <p>className: EasyMsControllerAspect</p>
 * <p>description: Controller层方法拦截器</p>
 *
 * @author guoguifang
 * @version 1.7.1
 * @date 2020/12/11 2:05 下午
 */
@Slf4j
public class EasyMsControllerAspect extends AbstractEasyMsMethodInterceptor {

    private static final Map<Method, Boolean> METHOD_IS_RETURN_RESPONSE_BODY_CACHE = new ConcurrentReferenceHashMap<>();

    private final EasyMsRestProperties easyMsRestProperties;

    public EasyMsControllerAspect(EasyMsRestProperties easyMsRestProperties) {
        this.easyMsRestProperties = easyMsRestProperties;
    }

    @Override
    protected Object intercept(MethodInvocation methodInvocation) throws Throwable {
        // 获取HttpServletRequest对象并判断是否为空，如果为空则跳过
        EasyMsRestContext easyMsRestContext = EasyMsRestSynchronizationManager.getEasyMsRestContext();
        HttpServletRequest request;
        if (easyMsRestContext == null || (request = easyMsRestContext.getHttpServletRequest()) == null) {
            return proceed(methodInvocation);
        }

        String requestPath = easyMsRestContext.getEasyMsRequestEntity().getRequestPath();
        Method method = methodInvocation.getMethod();
        String methodFullName = ReflectUtil.getMethodFullName(method);

        // 记录请求信息
        if (easyMsRestProperties.isLogRequest()) {
            RestInfo restInfo = easyMsRestContext.getRestInfo();
            if (restInfo == null) {
                restInfo = new RestInfo();
                easyMsRestContext.setRestInfo(restInfo);
            }
            restInfo.setRestControllerMethod(method);
            String queryString = request.getQueryString();

            // 获取方法参数值
            Object[] paramsArray = methodInvocation.getArguments();
            Map<String, Object> parameterMap = new HashMap<>(8);
            Parameter[] parameters = method.getParameters();
            if (parameters != null && parameters.length > 0) {
                for (int i = 0; i < parameters.length; i++) {
                    if (paramsArray.length > i && paramsArray[i] != null) {
                        Parameter parameter = parameters[i];
                        if (ServletRequest.class.isAssignableFrom(parameter.getType()) ||
                                ServletResponse.class.isAssignableFrom(parameter.getType())) {
                            continue;
                        }
                        if (parameter.getType().isAnnotationPresent(CustomToString4Log.class)) {
                            parameterMap.put(parameter.getName(), paramsArray[i].toString());
                        } else if (MultipartFile.class.isAssignableFrom(parameter.getType())) {
                            MultipartFile multipartFile = (MultipartFile) paramsArray[i];
                            parameterMap.put(parameter.getName(), multipartFile.getOriginalFilename());
                        } else if (!InputStreamSource.class.isAssignableFrom(parameter.getType())) {
                            parameterMap.put(parameter.getName(), paramsArray[i]);
                        }
                    }
                }
            }

            // 记录方法入参日志信息
            log.info("[Entry method: {}]-[request path: {}]{}-parameter data: {}.", methodFullName, requestPath,
                    StringUtils.isEmpty(queryString) ? "" : "-[query string: " + queryString + "]",
                    JsonUtil.toJSONString(parameterMap));
        }

        // 执行请求过程
        Object result = proceed(methodInvocation);

        // 将返回结果设置到rest上下文中
        easyMsRestContext.setResponseBody(result);

        // 判断是否是有@ResponseBody注解
        boolean isReturnResponseBody = isReturnResponseBody(method);
        if (isReturnResponseBody) {
            easyMsRestContext.setReturnResponseBody(true);
        }

        // 记录返回值日志信息
        boolean isRecordReturnData = true;
        if (easyMsRestProperties.isLogResponse()) {
            if (result != null) {
                Class<?> responseBodyClazz = null;
                if (HttpEntity.class.isAssignableFrom(method.getReturnType())) {
                    HttpEntity httpEntity = (HttpEntity) result;
                    Object body = httpEntity.getBody();
                    if (body != null) {
                        responseBodyClazz = body.getClass();
                    }
                } else {
                    responseBodyClazz = method.getReturnType();
                }
                if (responseBodyClazz != null && InputStreamSource.class.isAssignableFrom(responseBodyClazz)) {
                    isRecordReturnData = false;
                }
            }
            log.info("[Exit method: {}]-[request path: {}]{}.", methodFullName, requestPath,
                    isRecordReturnData ? "-return data: " + JsonUtil.toJSONString(result) : "");
        } else {
            isRecordReturnData = false;
        }
        easyMsRestContext.setNotRecordReturnData(!isRecordReturnData);
        return result;
    }

    private boolean isReturnResponseBody(Method method) {
        return METHOD_IS_RETURN_RESPONSE_BODY_CACHE.computeIfAbsent(method,
                m -> AnnotatedElementUtils.hasAnnotation(m.getDeclaringClass(), ResponseBody.class) ||
                        AnnotatedElementUtils.hasAnnotation(m, ResponseBody.class));
    }

}